/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.helper;

import java.util.ArrayList;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.uml2.uml.Association;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ElementImport;
import org.eclipse.uml2.uml.Extension;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.Transition;
import org.eclipse.uml2.uml.TransitionKind;
import org.eclipse.uml2.uml.UMLFactory;
import org.eclipse.uml2.uml.Vertex;

import com.cea.papyrus.core.helper.GraphEdgeHelper;
import com.cea.papyrus.core.helper.IGraphEdgeHelper;
import com.cea.papyrus.core.helper.ModelerHelper;
import com.cea.papyrus.core.helper.NodeClassHelper;
import com.cea.papyrus.core.helper.NodeHelper;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.core.utils.VectorCalculus;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphConnector;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Property;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;
/**
 * This is the helper to manage transition.
 * 
 * @author David Servat
 * @since 20 mar. 07
 *
 */
public class EdgeTransitionHelper extends GraphEdgeHelper implements IGraphEdgeHelper{

	public final static String TRANSITION_DISPLAY = "TransitionDisplay";
	
	public final static String TRANSITION_NOT_DISPLAY = "TransitionNotDisplay";
	
	public final static String TRANSITION_DISPLAY_GUARD = "TransitionDisplayGuard";
	
	public final static String TRANSITION_DISPLAY_TRIGGERS = "TransitionDisplayTriggers";

	public final static String TRANSITION_DISPLAY_EFFECT = "TransitionDisplayEffect";

	public final static String TRANSITION_DISPLAY_STEREOTYPES = "TransitionDisplayStereotypes";
	
	@Override
	public boolean canCreateGraphElement(GraphElement source){
		Element eltSource=null;
		if ((source instanceof Diagram)  ){
			if (((Uml1SemanticModelBridge)((Diagram)source).getOwner()).getElement()!=null){
				eltSource=((Uml1SemanticModelBridge)((Diagram)source).getOwner()).getElement();
				return canCreateUMLElement(eltSource);
			}
		}
		if (source.getSemanticModel()!=null ){
			if (((Uml1SemanticModelBridge)source.getSemanticModel()).getElement()!=null){
				eltSource=((Uml1SemanticModelBridge)source.getSemanticModel()).getElement();
				return canCreateUMLElement(eltSource);
			}
		}

		return false;
	}

	@Override
	public void endCreateGraphElement(Element umlsource, Element umlTarget,
			GraphEdge ge, Diagram diagram) {
			ArrayList di2suppliers = LookForElement.lookForGraphElementWODiagram(umlTarget, diagram, new ArrayList());
			ArrayList di2sources = LookForElement.lookForGraphElementWODiagram(umlsource, diagram, new ArrayList());
			Package topPackage = LookForElement.topPackage(((Uml1SemanticModelBridge)diagram.getOwner()).getElement());

			GraphElement source;
			GraphElement target;

			if((di2sources.size() != 0) && (di2suppliers.size() != 0)){
				source=(GraphNode) di2sources.get(0);
				target=(GraphNode) di2suppliers.get(0);
			}

			//the source does not exist
			else if((di2sources.size() == 0) && (di2suppliers.size() != 0)){
				//create the source
				Element ei = LookForElement.getElementImport(topPackage, umlsource);
				if(ei == null){
					ei=umlsource;
				}

				if(!(ei instanceof ElementImport)){
					ArrayList<GraphElement> found = new ArrayList<GraphElement>();
					LookForElement.lookForGraphElement(umlsource.getOwner(), diagram, found);
					if(found.size() != 0)
						source = ModelerHelper.getFactory().createGraphElement(ei, found.get(0), null, null);
					else
						source = ModelerHelper.getFactory().createGraphElement(ei, diagram, null, null);
				}
				else{
					NodeClassHelper nch= new NodeClassHelper();
					ArrayList<GraphElement> found = new ArrayList<GraphElement>();
					LookForElement.lookForGraphElement(umlsource.getOwner(), diagram, found);
					if(found.size() != 0)
						source = nch.createGraphElement(ei, found.get(0), null, null);
					else
						source = nch.createGraphElement(ei, diagram, null, null);
				}
				target = (GraphNode)di2suppliers.get(0);
				
				//position of the target
				if(source instanceof GraphNode){
					if(source.getContainer().equals(target.getContainer()))
						source.setPosition(new Point(target.getPosition().x-((GraphNode)source).getSize().width-SPACE_X,target.getPosition().y));
				}
			}
			//the target does not exist
			else if((di2sources.size() != 0) && (di2suppliers.size() == 0)){
				//create the supplier
				source = (GraphNode)di2sources.get(0);
				Element ei = LookForElement.getElementImport(topPackage, umlTarget);
				if(ei == null){
					ei = umlTarget;
				}

				if (!(ei instanceof ElementImport)){
					ArrayList<GraphElement> found = new ArrayList<GraphElement>();
					LookForElement.lookForGraphElement(umlTarget.getOwner(), diagram, found);
					if(found.size() != 0)
						target = ModelerHelper.getFactory().createGraphElement(ei, found.get(0), null, null);
					else
						target = ModelerHelper.getFactory().createGraphElement(ei, diagram, null, null);
				}
				else{
					NodeClassHelper nch= new NodeClassHelper();
					ArrayList<GraphElement> found = new ArrayList<GraphElement>();
					LookForElement.lookForGraphElement(umlTarget.getOwner(), diagram, found);
					if(found.size() != 0)
						target = nch.createGraphElement(ei, found.get(0), null, null);
					else
						target = nch.createGraphElement(ei, diagram, null, null);
				}
				
				//position of the target
				if(target instanceof GraphNode){
					if(source.getContainer().equals(target.getContainer()))
						target.setPosition(new Point(source.getPosition().x+((GraphNode)source).getSize().width+SPACE_X,source.getPosition().y));
				}
			}
			else{
				//create all
				ArrayList GraphNodes=null;
				Element ei = LookForElement.getElementImport(topPackage, umlsource);
				if(ei == null){
					ei = umlsource;
				}
				if(!(ei instanceof ElementImport)){
					ArrayList<GraphElement> found = new ArrayList<GraphElement>();
					LookForElement.lookForGraphElement(umlsource.getOwner(), diagram, found);
					if(found.size() != 0)
						source = ModelerHelper.getFactory().createGraphElement(ei, found.get(0), null, null);
					else
						source = ModelerHelper.getFactory().createGraphElement(ei, diagram, null, null);
				}
				else{
					NodeClassHelper nch= new NodeClassHelper();
					ArrayList<GraphElement> found = new ArrayList<GraphElement>();
					LookForElement.lookForGraphElement(umlsource.getOwner(), diagram, found);
					if(found.size() != 0)
						source = nch.createGraphElement(ei, found.get(0), null, null);
					else
						source = nch.createGraphElement(ei, diagram, null, null);
				}
				
				ei = null;
				if(umlTarget.getOwner() instanceof Package){
					ei = LookForElement.getElementImport(((Package)umlTarget.getOwner()), umlTarget);
				}
				if(ei == null){
					ei = umlTarget;
				}

				if (!(ei instanceof ElementImport)){
					ArrayList<GraphElement> found = new ArrayList<GraphElement>();
					LookForElement.lookForGraphElement(umlTarget.getOwner(), diagram, found);
					if(found.size() != 0)
						target = ModelerHelper.getFactory().createGraphElement(ei, found.get(0), null, null);
					else
						target = ModelerHelper.getFactory().createGraphElement(ei, diagram, null, null);
				}
				else{
					NodeClassHelper nch= new NodeClassHelper();
					ArrayList<GraphElement> found = new ArrayList<GraphElement>();
					LookForElement.lookForGraphElement(umlTarget.getOwner(), diagram, found);
					if(found.size() != 0)
						target = nch.createGraphElement(ei, found.get(0), null, null);
					else
						target = nch.createGraphElement(ei, diagram, null, null);
				}

				//position of the target
				if(target instanceof GraphNode){
					if(source.getContainer().equals(target.getContainer()))
						target.setPosition(new Point(source.getPosition().x+((GraphNode)source).getSize().width+SPACE_X,source.getPosition().y));
				}
			}

			//all element exist!
			//1. create connector
			GraphConnector sourceConnector=Di2Factory.eINSTANCE.createGraphConnector();
			GraphConnector targetConnector=Di2Factory.eINSTANCE.createGraphConnector();

			//2. calculate position for each element

			//2.1 position of the source connector
			Point p = new Point(((GraphNode)source).getPosition());
			if(source.getContainer() instanceof Diagram)
				p = LookForElement.calculateAbsolutePosition((GraphNode)source, new Point(0,0));
			sourceConnector.setPosition(new Point (p.x+((GraphNode)source).getSize().width, p.y+((GraphNode)source).getSize().height/2));
			//2.2 position if the target Connector
			p = new Point(((GraphNode)target).getPosition());
			if(target.getContainer() instanceof Diagram)
				p = LookForElement.calculateAbsolutePosition((GraphNode)target, new Point(0,0));
			targetConnector.setPosition(new Point(p.x, p.y+((GraphNode)target).getSize().height/2));

			//3. Connection of all element
			//3.1 connect anchor to graphEdge
			((GraphEdge)ge).getAnchor().add(SOURCE,sourceConnector);
			((GraphEdge)ge).getAnchor().add(TARGET,targetConnector);
			((GraphConnector)ge.getAnchor().get(SOURCE)).setGraphElement(source);
			((GraphConnector)ge.getAnchor().get(TARGET)).setGraphElement(target);
			if( ((Uml1SemanticModelBridge)ge.getSemanticModel()).getElement() instanceof Extension){
				VectorCalculus.setGoodPositionForExtension(ge,(GraphNode)source,(GraphNode)target);
			} else if( ((Uml1SemanticModelBridge)ge.getSemanticModel()).getElement() instanceof Association){
				VectorCalculus.setGoodPositionForAssociation(ge,(GraphNode)source,(GraphNode)target);}

			Debug.debug(this,"   target dependency link connected", channel);
			((GraphNode)source.getContainer()).getContained().add(ge);
	}
	
	/*
	 * (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.NodeFactory#createGraphNodeWithSemantic(org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param umlChild 
	 * @param size 
	 * @param location 
	 * @param parent 
	 * 
	 * @return 
	 */
	@Override
	public GraphElement createGraphElement(Element umlChild, GraphElement parent, Point location, Dimension size) {

		GraphElement graphElement=super.createGraphElement(umlChild, null, null, null);
		NodeHelper nf=new NodeHelper();
		
		//name of the dependency
		GraphElement graphNode=nf.createGraphElement(null, graphElement, new Point(0, 10), null);
		graphNode.setFontSize(9);
		graphNode.setIsVisible(false);

		//triggers guard effect
		graphNode=nf.createGraphElement(null, graphElement, new Point(0, 20), null);
		graphNode.setFontSize(9);

		//set display properties
		Property property = graphNode.getProperty(TRANSITION_DISPLAY_GUARD);
		if(property == null){
			// Create a property for this graph node to hold display property
			property=Di2Factory.eINSTANCE.createProperty();
			property.setKey(TRANSITION_DISPLAY_GUARD);
			property.setValue(TRANSITION_NOT_DISPLAY);		
			graphNode.getProperty().add(property);
		} 
		else{
			property.setValue(TRANSITION_NOT_DISPLAY);	
		}
		
		property = graphNode.getProperty(TRANSITION_DISPLAY_TRIGGERS);
		if(property == null){
			//create a property for this graph node to hold display property
			property=Di2Factory.eINSTANCE.createProperty();
			property.setKey(TRANSITION_DISPLAY_TRIGGERS);
			property.setValue(TRANSITION_NOT_DISPLAY);		
			graphNode.getProperty().add(property);
		}
		else{
			property.setValue(TRANSITION_NOT_DISPLAY);	
		}
		
		property = graphNode.getProperty(TRANSITION_DISPLAY_EFFECT);
		if(property == null){
			//create a property for this graph node to hold display property
			property=Di2Factory.eINSTANCE.createProperty();
			property.setKey(TRANSITION_DISPLAY_EFFECT);
			property.setValue(TRANSITION_NOT_DISPLAY);		
			graphNode.getProperty().add(property);
		}
		else{
			property.setValue(TRANSITION_NOT_DISPLAY);	
		}
		
		property = graphNode.getProperty(TRANSITION_DISPLAY_STEREOTYPES);
		if(property == null){
			//create a property for this graph node to hold display property
			property=Di2Factory.eINSTANCE.createProperty();
			property.setKey(TRANSITION_DISPLAY_STEREOTYPES);
			property.setValue(TRANSITION_DISPLAY);		
			graphNode.getProperty().add(property);
		}
		else{
			property.setValue(TRANSITION_NOT_DISPLAY);	
		}
		//stereotype of dependency
		graphNode=nf.createGraphElement(null, graphElement, new Point(0, 30), null);
		graphNode.setFontSize(9);

		//the parent has to be connected in last, because a event will be triggered.
		if(parent!=null){
			parent.getContained().add(graphElement);
		}

		return graphElement;	
	}

	/////////////////////////////////////////////////////////////////////////////////
	//                       UMLManagement
	/////////////////////////////////////////////////////////////////////////////////
	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.GraphEdgeFactory#createUMLElement(java.lang.String, org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param umlParent 
	 * @param name 
	 * 
	 * @return 
	 */
	@Override
	public  org.eclipse.uml2.uml.Element createUMLElement(String name, org.eclipse.uml2.uml.Element umlParent){
		Debug.debug(this,"create transition", channel); //$NON-NLS-1$
		Transition transition= UMLFactory.eINSTANCE.createTransition();
		transition.setName(getNewUMLElementName(umlParent, transition));
		transition.setKind(TransitionKind.EXTERNAL_LITERAL);
		if (umlParent instanceof Region){
			((Region)umlParent).getTransitions().add(transition);
		}

		return transition;		
	}


	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.GraphEdgeFactory#canCreateUMLElement(org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 */
	@Override
	public boolean canCreateUMLElement(Element umlParent) {
		if(umlParent instanceof Region) {
			return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.GraphEdgeFactory#addUMLElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 */
	@Override
	public boolean addUMLElement(Element element, Element umlParent) {
		if(umlParent instanceof Region) {
			return ((Region)umlParent).getTransitions().add((Transition)element);
		}

		return false;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.GraphEdgeFactory#removeUMLElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 */
	@Override
	public boolean removeUMLElement(Element element, Element umlParent) {
		if(umlParent instanceof Region) {
			return ((Region)umlParent).getTransitions().remove(element);
		}

		return false;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.GraphEdgeFactory#getBaseString()
	 */
	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	public String getBaseString() {
		return "Transition_"; //$NON-NLS-1$
	}

	/////////////////////////////////////////////////////////////////////////////////
	//                       LINK MANAGEMENT
	/////////////////////////////////////////////////////////////////////////////////

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.GraphEdgeFactory#connect(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param target 
	 * @param link 
	 * @param source 
	 * 
	 * @return 
	 */
	@Override
	public boolean connect(Element source, Element target, Element link){
		Transition transition = (Transition)link;
		transition.setSource(null);
		transition.setSource((Vertex)source);
		transition.setTarget(null);
		transition.setTarget((Vertex)target);
		transition.setName(getNewUMLElementName(link.getOwner(), transition));
		return true;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.GraphEdgeFactory#deconnect(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param target 
	 * @param link 
	 * @param source 
	 * 
	 * @return 
	 */
	@Override
	public boolean deconnect(Element source, Element target, Element link){
		Transition transition=(Transition)link;
		transition.setSource(null);
		transition.setTarget(null);
		transition.setName(getNewUMLElementName(link.getOwner(), transition));
		return true;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.IGraphEdgeFactory#canExecute(com.cea.papyrus.diagraminterchange2.di2.GraphNode, com.cea.papyrus.diagraminterchange2.di2.GraphNode)
	 */	
	/**
	 * 
	 * 
	 * @param target 
	 * @param source 
	 * 
	 * @return 
	 */
	@Override
	public boolean canConnect(Element source, Element target){
		//if no source or target found
		if((source == null) && (target == null)){
			return false;
		}
		//if only source is not null
		if((source != null) && (target == null)){
			if(source instanceof Vertex){
				return true;
			}
		}
		//general case
		else if((source instanceof Vertex) && (target instanceof Vertex)){
//			Element elt1 = source.getOwner();
//			Element elt2 = target.getOwner();
//			while((elt1 != null) && !(elt1 instanceof StateMachine))
//				elt1 = elt1.getOwner();
//			while((elt2 != null) && !(elt2 instanceof StateMachine))
//				elt2 = elt2.getOwner();
//			if(elt1.equals(elt2))
				return true;
//			else
//				return false;
		}
		//all other cases
		return false;
	}
	
	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#getNewUMLElementName(org.eclipse.uml2.uml.Element)
	 */
	@Override
	public String getNewUMLElementName(Element umlParent){
		String name = ""; //$NON-NLS-1$

		//i <10000: avoid infinite loops
		for(int i=0; i< 10001; i++ ) {
			name = getBaseString()+i;
			if(umlParent == null)
				return name;
			if(((Region)umlParent).getTransition(name) == null)
				return name;
		}
		return getBaseString()+"X"; //$NON-NLS-1$
	}

	/**
	 * Same as 
	 * @see com.cea.papyrus.common.helper.NodeHelper#getNewUMLElementName(org.eclipse.uml2.uml.Element)
	 * but takes into account source and target
	 */
	public String getNewUMLElementName(Element umlParent, Transition transition){
		String name = ""; //$NON-NLS-1$

		//i <10000: avoid infinite loops
		for(int i=0; i< 10001; i++ ) {
			if((transition.getSource() != null) && (transition.getTarget() != null))
				name = "From_" + transition.getSource().getName()+"_to_"+transition.getTarget().getName();
			name += "_"+getBaseString()+i;
			if(umlParent == null)
				return name;
			if(((Region)umlParent).getTransition(name) == null)
				return name;
		}
		return getBaseString()+"X"; //$NON-NLS-1$
	}

}
